package com.apes.framework.jpa.repository;

import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.BaseModel;
import com.apes.framework.jpa.Model;
import com.apes.framework.jpa.generator.service.SequenceService;
import com.apes.framework.jpa.logger.ApesRelation;
import com.apes.framework.jpa.logger.ApesRelationRepository;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.plugin.outbox.OutboxEventService;
import com.apes.framework.util.JpaUtil;
import com.apes.framework.util.ReflectUtil;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;


public class ExtendedJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CustomRepository<T, ID> {
    private final JpaEntityInformation<T, ?> entityInformation;
    private final EntityManager em;
    private final PersistenceProvider provider;

    public ExtendedJpaRepository(JpaEntityInformation<T, ?> information, EntityManager entityManager) {
        super(information, entityManager);
        this.entityInformation = information;
        this.em = entityManager;
        this.provider = PersistenceProvider.fromEntityManager(entityManager);
    }

    @Override
    public <S extends T> S save(S entity) {
        S result;
        if (entity instanceof Model && StringUtils.isEmpty(((Model) entity).getInnerBusinessType())) {
            ((Model) entity).setInnerBusinessType(entity.getClass().getSimpleName());
        }
        boolean newed = isNew(entity);
        SpringManager.getBean(SequenceService.class).setId(entity);
        if (newed) {
            if (entity instanceof Model && ((Model) entity).getCreator() == null) {
                ((Model) entity).setCreator(((Model) entity).getOperator());
            }
            em.persist(entity);
            result = entity;
        } else {
            if (entity instanceof BaseModel) {
                ((BaseModel) entity).audit();
            }
            if (entity instanceof Model && ((Model) entity).getUpdater() == null) {
                ((Model) entity).setUpdater(((Model) entity).getOperator());
            }
            result = em.merge(entity);
            if (result instanceof BaseModel) {
                ((BaseModel) result).setAudit(((BaseModel) entity).getAudit());
            }
        }
        if (result instanceof Model) ((Model) result).setInnerBusinessKey(JpaUtil.getIdToStr((ApesBean) result));
       // relation((ApesBean) result);
        return result;
    }

    private void relation(ApesBean entity) {
        if (!(entity instanceof Model)) return;
        String businessKey = ReflectUtil.getFieldValue(entity, "source");
        String businessType = null;
        if (StringUtils.hasText(businessKey)) {
            businessType = ReflectUtil.getFieldValue(entity, "sourceBusinessType");
            if (StringUtils.isEmpty(businessType)) {
                businessType = ReflectUtil.getFieldValue(entity, "sourceType");
            }
        } else {
            businessKey = ReflectUtil.getFieldValue(entity, "origin");
            if (StringUtils.hasText(businessKey)) {
                businessType = ReflectUtil.getFieldValue(entity, "originBusinessType");
            }
        }

        if (StringUtils.hasText(businessKey) && StringUtils.hasText(businessType)) {
            String id = JpaUtil.getIdToStr(entity);
            Date createDate = ReflectUtil.getFieldValue(entity, "createDate");
            if (createDate == null) createDate = new Date();
            String type = JpaUtil.getImplementation(entity).getClass().getSimpleName();
            ApesRelation relation = SpringManager.getBean(ApesRelationRepository.class).findOne(
                    JpaDsl.toCriteriaByEq("businessKey", id, "businessType", type)).orElse(null);
            if (relation != null) return;
            relation = new ApesRelation(type, id, createDate, businessType, businessKey);
            JpaUtil.getRepository(ApesRelation.class.getName()).save(relation);

            Class clazz = JpaUtil.getDomainClass(businessType);
            if (clazz == null) return;
            CustomRepository repository = JpaUtil.getRepository(clazz.getName());
            Object source = repository.findOne(JpaUtil.getId(clazz.getSimpleName(), businessKey));
            if (source != null) relation((ApesBean) source);
            relation = SpringManager.getBean(ApesRelationRepository.class).findOne(
                    JpaDsl.toCriteriaByEq("businessKey", businessKey, "businessType", businessType)).orElse(null);
            if (relation == null) {
                if (Objects.isNull(source)){
                    createDate = new Date();
                } else {
                    createDate = ReflectUtil.getFieldValue(source, "createDate");
                }
                relation = new ApesRelation(businessType, businessKey, createDate, null, null);
                JpaUtil.getRepository(ApesRelation.class.getName()).save(relation);
            }
        }
    }

    private boolean isNew(T entity) {
        ID id = (ID) entityInformation.getId(entity);
        if (id instanceof String) return StringUtils.isEmpty(id);
        return entityInformation.isNew(entity);
    }

    @Override
    public <S extends T> S saveAndFlush(S entity) {
        S result = super.saveAndFlush(entity);
        SpringManager.getBean(OutboxEventService.class).sendSaveEvent(entity);
        return result;
    }

    @Override
    public void delete(T entity) {
        super.delete(entity);
        SpringManager.getBean(OutboxEventService.class).sendDeleteEvent(entity);
    }

    @Override
    public int exec(String sql) {
        return exec(sql, null);
    }

    @Override
    public int exec(String sql, Map<String, Object> params) {
        Query query = em.createNativeQuery(sql);
        if (params != null) {
            params.forEach((k, v) -> query.setParameter(k, v));
        }
        return query.executeUpdate();
    }

    @Override
    public List findAll(String sql) {
        return findAll(sql, null);
    }

    @Override
    public List findAll(String sql, Map<String, Object> params) {
        Query query = em.createNativeQuery(sql);
        if (params != null) {
            query.getParameters().stream().forEach(parameter -> {
                query.setParameter(parameter.getName(), params.get(parameter.getName()));
            });
        }
        return query.getResultList();
    }

    @Override
    public T findOne(ID id) {
        return findById(id).orElse(null);
    }

    @Override
    public void delete(ID id) {
        deleteById(id);
    }

    @Override
    public boolean exists(ID id) {
        return findOne(id) != null;
    }

    @Override
    public <S extends T> List<S> save(Iterable<S> entities) {
        return saveAll(entities);
    }
}
